package contract

import (
	"encoding/json"
	"errors"
	"fmt"
	"github.com/hyperledger/fabric/core/chaincode/shim"
	"github.com/hyperledger/fabric/core/chaincode/shim/ext/cid"
	"github.com/hyperledger/fabric/protos/peer"
	"reflect"
)

type Contract struct {
	Participants []Participant
	Assets       []Asset
	Transactions []Transaction
}

func (sc *SmartContract) Init(stub shim.ChaincodeStubInterface) peer.Response {
	return shim.Success([]byte("smart contract init success"))
}

func (sc *SmartContract) Invoke(stub shim.ChaincodeStubInterface) peer.Response {
	args := stub.GetArgs()
	var req requestArgs
	err := json.Unmarshal(args[0], &req)
	if err != nil {
		return shim.Error("invalid request args")
	}

	asset, ok := sc.assets[req.Asset]
	if !ok {
		return shim.Error("not found asset " + req.Asset)
	}
	ctx := newContext(stub)
	callFunc, ok := asset.methods[req.Func]
	if !ok {
		return shim.Error("not found method")
	}

	retVal := callFunc.Func.Call([]reflect.Value{asset.value, reflect.ValueOf(ctx)})
	resp, ok := retVal[0].Interface().(*response)
	if !ok {
		return shim.Error("invalid response")
	}
	return resp.Response()
}

type SmartContract struct {
	context *Context
	assets  map[string]*assetCollection
}

func New() *SmartContract {
	return &SmartContract{
		assets: make(map[string]*assetCollection),
	}
}

func (sc *SmartContract) AddAsset(asset Asset) error {
	typ := reflect.TypeOf(asset)
	name := typ.Name()
	if _, ok := sc.assets[name]; ok {
		return fmt.Errorf("asset [%s] has exist", name)
	}
	sc.assets[name] = newAssetCollection(asset)
	return nil
}

func (sc *SmartContract) Deploy() error {
	return shim.Start(sc)
}

type requestArgs struct {
	Asset string      `json:"asset"`
	Func  string      `json:"func"`
	Args  interface{} `json:"args"`
}

type Context struct {
	stub   shim.ChaincodeStubInterface
	mspid  string
	assets map[string]Asset
	args   []byte
}

func newContext(stub shim.ChaincodeStubInterface) *Context {
	return &Context{
		stub: stub,
	}
}

func (c *Context) getMspID() (string, error) {
	clientId, err := cid.New(c.stub)
	if err != nil {
		return "", err
	}
	return clientId.GetMSPID()
}

func (c *Context) GetAttribute(key string) (string, error) {
	return "", nil
}
type Mode int8

const (
	Mode_READ   Mode = 8
	Mode_WRITE       = 4
	Mode_UPDATE      = 2
	Mode_DELETE      = 1
	Mode_All         = 15
)

var (
	ErrNotReadable = errors.New("not readable")
)

// authority to asset
// 0 0 0 0
// R W U D : Read, Write, Update, Delete
func (c *Context) CanBeRead(asset Asset) bool {
	perms := asset.Permission()
	perm, ok := perms[c.mspid]
	if !ok {
		return false
	}
	return (perm & Mode_READ) == Mode_READ
}

func (c *Context) CanBeWrite(asset Asset) bool {
	perms := asset.Permission()
	perm, ok := perms[c.mspid]
	if !ok {
		return false
	}
	return (perm & Mode_WRITE) == Mode_WRITE
}

func (c *Context) CanBeUpdated(asset Asset) bool {
	perms := asset.Permission()
	perm, ok := perms[c.mspid]
	if !ok {
		return false
	}
	return (perm & Mode_UPDATE) == Mode_UPDATE
}

func (c *Context) CanBeDeleted(asset Asset) bool {
	perms := asset.Permission()
	perm, ok := perms[c.mspid]
	if !ok {
		return false
	}
	return (perm & Mode_DELETE) == Mode_DELETE
}

func (c *Context) GetArgs() []byte {
	return c.args
}

func (c *Context) Get(key string) ([]byte, error) {
	return c.stub.GetState(key)
}

func (c *Context) Set(key string, val []byte) error {
	return c.stub.PutState(key, val)
}

func (c *Context) Delete(key string) error {
	return c.stub.DelState(key)
}

func (c *Context) GetFunc(handle func(key string, data []byte) error) error {
	query, _ := c.stub.GetStateByRange("", "")
	defer query.Close()
	for query.HasNext() {
		value, _ := query.Next()
		handle(value.Key, value.Value)
	}
	return nil
}

func (c *Context) PutAsset(asset Asset) error {
	data, err := json.Marshal(asset)
	if err != nil {
		return err
	}
	return c.Set(asset.Index(), data)
}

func (c *Context) GetAsset(key string, asset Asset) error {
	data, err := c.Get(key)
	if err != nil {
		return err
	}
	return json.Unmarshal(data, asset)
}

func (c *Context) Error(err error) *response {
	return &response{
		Status:  shim.ERRORTHRESHOLD,
		Message: err.Error(),
		Payload: nil,
	}
}

func (c *Context) Success(payload []byte) *response {
	return &response{
		Status:  shim.OK,
		Message: "success",
		Payload: payload,
	}
}

type Response interface {
	GetMessage() string
	GetPayload() []byte
	GetStatus() int32
}

type response struct {
	Status  int32
	Message string
	Payload []byte
}

func (resp response) GetMessage() string {
	return resp.Message
}

func (resp response) GetPayload() []byte {
	return resp.Payload
}

func (resp response) GetStatus() int32 {
	return resp.Status
}

func (resp response) Response() peer.Response {
	return peer.Response{
		Status:  resp.Status,
		Message: resp.Message,
		Payload: resp.Payload,
	}
}
